iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 14
1
AI & Data

D3.js資料視覺化的浪漫突進系列 第 14

Day14 d3.bisector找到資料正確的位置

  • 分享至 

  • xImage
  •  

D3js d3.bisector找到資料正確的位置

用途

d3.bisector是用來尋找某值對應一個陣列資料內的正確位置或最接近的位置,我在真實案例中使用到的地方是特定X軸對應的Y軸值。

d3.bisector

範例:

const datas = [0, 1, 1, 2];

const index = d3.bisect(datas, 1);

console.log(index); // 3

透過d3.bisector可以找出1插入的話,會位於第3個。其實就是2前面。

bisectRight

範例:

const datas = [0, 1, 1, 2];

const index = d3.bisectRight(datas, 1);

console.log(index); // 3

透過d3.bisectRight可以找出1插入的話,並插入資料源的右側,會位於第3個。

bisectLeft

範例:

const datas = [0, 1, 1, 2];

const index = d3.bisectLeft(datas, 1);

console.log(index); // 1

透過d3.bisectRight可以找出1插入的話,並插入資料源的左側,會位於第1個。

應用

範例:前半部可跳過


const datas = [
  {
    value: 10,
    date: new Date('2020/08/25 01:00:00')
  },
  {
    value: 123,
    date: new Date('2020/08/25 02:00:00')
  },
  {
    value: 56,
    date: new Date('2020/08/25 03:00:00')
  },
  {
    value: 98,
    date: new Date('2020/08/25 05:00:00')
  },
  {
    value: 30,
    date: new Date('2020/08/25 06:00:00')
  },
  {
    value: 156,
    date: new Date('2020/08/25 07:00:00')
  },
  {
    value: 20,
    date: new Date('2020/08/25 08:00:00')
  },
  {
    value: 12,
    date: new Date('2020/08/25 09:00:00')
  }
]

          

let width = 800;
let height = 600;
let padding = 50;
let innerWidth = width - padding * 2;
let innerHeight = height - padding * 2;

let svg = d3.select('svg')
            .attr('width', width)
            .attr('height', height)
            .on('mousemove', mousemove)

let rootLayer = svg.append('g')
                  .attr('transform', `translate(${padding}, ${padding})`);
let axisLayer = rootLayer.append('g');
let lineLayer = rootLayer.append('g');

let xExtent = d3.extent(datas.map(data => data.date));
let yExtent = d3.extent(datas.map(data => data.value));

let xScale = d3.scaleTime().range([0, innerWidth]).domain(xExtent);
let yScale = d3.scaleLinear().range([innerHeight, 0]).domain(yExtent);

let xAxis = d3.axisBottom().scale(xScale).tickSize(-innerHeight);
let xAxisLayer = axisLayer.append('g').attr('transform', `translate(0, ${innerHeight})`).call(xAxis);
let yAxis = d3.axisLeft().scale(yScale).tickSize(-innerWidth);
let yAxisLayer = axisLayer.append('g').call(yAxis);
let circleLayer = rootLayer.append('g');

let line = d3.line()
            .x(data => xScale(data.date))
            .y(data => yScale(data.value))

let lines = lineLayer.selectAll('path')
                    .data([datas], data => data.date);

lines.enter()
    .append('path')
    .attr('d', line)
    .attr('fill', 'none')
    .attr('stroke', 'red')
    .attr('stroke-width', 2)

// 這部分是重點
// 先產生一個bisect function. 並會目前得到的日期的左側位置。
const bisect = d3.bisector((b) => b.date).left;


function mousemove() {
    const x = d3.pointer(event)[0] - padding;
    const y = d3.pointer(event)[1] - padding;
    const targetTime =  xScale.invert(x);
    // 推算出目前hover位置的Time
    const i = bisect(datas, targetTime);
    // 推出目前位置Time的對應第幾個
    
    // 先移除Layer上的其他圓形
    circleLayer.selectAll('*').remove();
    
    // 直接append`Circle`並`cx``cy`帶入剛剛取得的序列對應的資料位置。
    circleLayer
      .append('circle')
      .attr('r', 5)
      .attr('cy', yScale(datas[i].value))
      .attr('cx', xScale(datas[i].date))
      .attr('fill', 'blue')
  
}

結論

應用場景非常實際,當hover時顯示特定附近的資料源,當然還有更多神奇的應用,像是一次取得特定位置x多個資料源的資料,等等...

範例Codepen

參考

d3-array
d3-bisect


上一篇
Day13 D3js d3.invert反轉Scale取值
下一篇
Day15 D3js d3.hierarchy結構資料的好幫手
系列文
D3.js資料視覺化的浪漫突進30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言